#ifndef __JSMR_H__
#define __JSMR_H__

#include <map>
#include <set>
#include <vector>

#include <v8.h>

namespace JSMR {

// A macro to disallow the evil copy constructor and operator= functions
// This should be used in the private: declarations for a class
#define DISALLOW_COPY_AND_ASSIGN(TypeName)      \
  TypeName(const TypeName&);                    \
    void operator=(const TypeName&) 

typedef void (*MapEmitter)(const std::string &key,
                           const std::string &value);

enum LogLevel {
  FATAL = 0,
  ERROR = 1,
  WARN  = 2,
  INFO  = 3,
  LAST_LOG_LEVEL = INFO
};

// Generates a printable name for a LogLevel.
const std::string &LogLevelName(LogLevel level);

// TODO(crutcher): document
class CharBufferStringResource :
    public v8::String::ExternalAsciiStringResource {
 public:
  CharBufferStringResource(char *data, size_t length);

  virtual ~CharBufferStringResource();
  virtual const char* data() const;
  virtual size_t length() const;

 private:
  const char *data_;
  const size_t length_;
};

// TODO(nathan,crutcher,anyone) This interface requires too many string copies.
// FIX THAT!
class MapContext {
 public:  
  virtual const std::string &GetInput() = 0;
  virtual void Emit(const std::string &key, const std::string &value) = 0;
};


class ReduceContext : public MapContext {
 public: 
  virtual const std::string &GetKey() = 0;
  virtual bool NextValue() = 0;
};

// This class provides the bindings needed by the JSContainer to perform
// operations.
class OperationEnvironment {
 public:
  OperationEnvironment() {};

  virtual ~OperationEnvironment() {};

  // Handle Fatal events.
  // If this function returns, abort() will be called.
  virtual void OnFatal() {};
  
  // Reads a file from 'path'.
  // Should always succeed, and should Log(FATAL, ...) on failure.
  virtual v8::Handle<v8::String> ReadFile(
      const std::vector<std::string> &search_paths,
      const std::string &path) = 0;
  
  // Log an event. Aborts on FATAL logs.
  // Subclasses which need to override this method should override LogImpl
  // instead.
  void Log(
      LogLevel level,
      const std::string &source_path,
      unsigned int line_number,
      const std::string &msg);

 protected:
  // Implementation of Log reporting.
  // This method is called by Log(), and is only responsible for logging.
  // Handling of FATAL events will be handled by Log().
  virtual void LogImpl(
      LogLevel level,
      const std::string &source_path,
      unsigned int line_number,
      const std::string &msg);

 private:
  DISALLOW_COPY_AND_ASSIGN(OperationEnvironment);
};


class DefaultOperationEnvironment : public OperationEnvironment {
 public:
  // Reads files from the local POSIX environment.
  virtual v8::Handle<v8::String> ReadFile(
      const std::vector<std::string> &search_paths,
      const std::string &path);
};

/**
 * Abstract class used to assocaite an appropritate 'ReadFile' definition with
 * the correct protocol.  Used to associate a protocol name (e.g.
 * 'http', 'file', 'hdfs', e.g.) with a file reader.
 */
class ProtocolHandler {
 public:

  /**
   * Constructor that does nothing
   */
  ProtocolHandler();

  /**
   * Constructor that * the correct protocol.  Used to associate a protocol 
   * name (e.g.  * 'http', 'file', 'hdfs', e.g.) with a file reader.
   * @param protocol_name The name of the protocol, e.g. 'hdfs'
   */
  ProtocolHandler(std::string protocol_name);

  /**
   * Returns the protocol name associated with this protocol.
   * @return The string that's associated with this protocol.
   */
  virtual std::string GetProtocolName();

  /**
   * Abstract method that MUST be overridden.  Responsible for reading the
   * contents of a file (defers so some other protocol)
   * @param path path to the file
   * @return strins containing contents of file
   */
  virtual v8::Handle<v8::String> ReadFile(const std::string &path) = 0;
 protected:
  
  /**
   * The string representing the protocol name (e.g. "hdfs" or "file")
   */
  std::string protocol_name_;
};


/**
 * This is a sub-class of the abstract class ProtocolHandler, specifically for
 * reading local files.  It is associated with the protocol name 'file'
 */
class LocalFileHandler : public ProtocolHandler {
 public:

  /**
   * This is a sub-class of the abstract class ProtocolHandler, specifically for
   * reading local files.  It is associated with the protocol name 'file'
   */
  LocalFileHandler();

  /**
   * Read a file from the local file system.
   * @param long_path The full path to the file on the local file system
   * @return contents of the file read.
   */
  virtual v8::Handle<v8::String> ReadFile(const std::string &path);
};


/**
 * JSContainer 
 */
class JSContainer {
 public:
  // 'op_environment' MUST outlive JSContainer
  /**
   * Constructor for JSContainer
   * @param op_environment a pointer to the OperationEnvironment
   * @param config_json A string being passed in which contains JSON defining the
   * run-time configuration.
   */
  JSContainer(OperationEnvironment *op_environment,
              const std::string &config_json);

  /**
   * Destructor for a JSContainer.  
   */
  ~JSContainer();


  /**
   * Boot the container by running all scripts listed in JSMR.config.scripts
   */
  void Boot();

  /**
   * Called by 'RequireCallback'.  Evaluates and runs the script that was
   * required.
   * @param path the path passed in via 'require(path)'
   */
  void RunScript(const std::string &path);

  void RunMap(MapContext *map_context);

  void RunReduce(ReduceContext *reduce_context);

 private:
  /**
   * Private method for reporting exceptions.
   * @param level The log level to report at (e.g. 'WARN')
   * @param try_catch The 'TryCatch' which contains the exception information.
   */

  void ReportException(LogLevel level, v8::TryCatch *try_catch);

  /** 
   * Pass through to the logger from the operation environment.
   * @param level the log level to log at (e.g. 'WARN')
   * @param source the name of the file that 'Log' was called from.
   * @param line the line number where 'Log' was called.
   * @param msg the message to report.
   */
  void Log(LogLevel level, const std::string &source,
           unsigned int line, const std::string &msg);

  /**
   * Private method for parsing a string containing JSON and returning a
   * JavaScript Value which represents it.
   * @param str the string containing the JSON
   * @return the evaluated JSON as a JavaScript value
   */ 
  v8::Handle<v8::Value> ParseJSON(v8::Handle<v8::String> str);

  /**
   * Called from within the JavaScript environment when the user calls:
   *    jsmr.log(...)
   * @param args the arguments to the JavaScript function.  These should include
   * the log level, the message, and the stack depth to look up line numbers on.
   * @return v8::Undefined (effectively a void function)
   */
  static v8::Handle<v8::Value> LogCallback(const v8::Arguments& args);

  /**
   * This is called by the JavaScript environment when the user calls:
   *    require(...)
   * @param args list of scripts to load (as strings).
   * @return v8::Undefined() (effectively a void function)
   */
  static v8::Handle<v8::Value> RequireCallback(const v8::Arguments& args);

  /**
   * This is called by the JavaScript environment when the user calls:
   *    emit(key, value)
   * calls the Emit method from the current Map Context.
   * @param args should be a key and a value
   * @return v8::Undefined() (essentially a void function)
   */
  static v8::Handle<v8::Value> EmitCallback(const v8::Arguments& args);

  DISALLOW_COPY_AND_ASSIGN(JSContainer);

  OperationEnvironment *op_environment_;
  v8::Persistent<v8::Context> context_;
  v8::Persistent<v8::Function> source_array_func_;

  // The 'JSMR' object in the container.
  v8::Persistent<v8::Object> jsmr_obj_;
  // The 'JSMR.config' object in the container.
  v8::Persistent<v8::Object> config_obj_;

  std::set<std::string> loaded_scripts_;
};

}  // namespace JSMR

#endif  // __JSMR_H__
